package cz.muni.fi.pa165.sportclubmanager.dao;

import cz.muni.fi.pa165.sportclubmanager.entity.Player;
import cz.muni.fi.pa165.sportclubmanager.entity.PlayerOnRoster;
import cz.muni.fi.pa165.sportclubmanager.entity.Team;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.springframework.stereotype.Repository;

/**
 *
 * @author mprokes
 */
@Repository
public class PlayerOnRosterDAOImpl implements PlayerOnRosterDAO {

    @PersistenceContext
    private EntityManager em;

    public PlayerOnRosterDAOImpl() {
    }

    @Override
    public void create(PlayerOnRoster por) {
        if (por == null || !notNull(por.getTeam(), por.getPlayer())) {
            throw new IllegalArgumentException("PlayerOnRoster and team and player attributes must not be null.");
        }
        if (!notNull(por.getTeam().getId(), por.getPlayer().getId())) {
            throw new IllegalArgumentException("Team's id and Player's id must not be null.");
        }
        checkConstraints(por, true);
        em.persist(por);
    }

    @Override
    public void remove(PlayerOnRoster por) {
        if (por == null || por.getId() == null) {
            throw new IllegalArgumentException("PlayerOnRoster and its id must not be null.");
        }
        if (em.contains(por)) {
            em.remove(por);
        }
    }

    @Override
    public void update(PlayerOnRoster por) {
        if (por == null || !notNull(por.getId(), por.getTeam(), por.getPlayer())) {
            throw new IllegalArgumentException("PlayerOnRoster and all its attributes must not be null.");
        }
        if (!notNull(por.getTeam().getId(), por.getPlayer().getId())) {
            throw new IllegalArgumentException("Team id and Player id must not be null.");
        }
        checkConstraints(por, false);
        em.merge(por);
    }

    @Override
    public List<PlayerOnRoster> findAll() {
        TypedQuery<PlayerOnRoster> query = em.createQuery("SELECT por FROM PlayerOnRoster por", PlayerOnRoster.class);
        return query.getResultList();
    }

    @Override
    public PlayerOnRoster find(Long id) {
        return em.find(PlayerOnRoster.class, id);
    }

    @Override
    public List<PlayerOnRoster> findAllWithTeam(Team team) {
        if (team == null || team.getId() == null) {
            throw new IllegalArgumentException("Team and team.id must not be null.");
        }
        TypedQuery<PlayerOnRoster> query = em.createQuery("SELECT por FROM PlayerOnRoster por"
                + " WHERE por.team.id = :team_id", PlayerOnRoster.class);
        query.setParameter("team_id", team.getId());
        return query.getResultList();
    }

    @Override
    public List<PlayerOnRoster> findAllWithPlayer(Player player) {
        if (player == null || player.getId() == null) {
            throw new IllegalArgumentException("Player and player.id must not be null.");
        }
        TypedQuery<PlayerOnRoster> query = em.createQuery("SELECT por FROM PlayerOnRoster por"
                + " WHERE por.player.id = :player_id", PlayerOnRoster.class);
        query.setParameter("player_id", player.getId());
        return query.getResultList();
    }

    private boolean notNull(Object... vals) {
        for (Object object : vals) {
            if (object == null) {
                return false;
            }
        }
        return true;
    }

    /**
     * Performs data integrity check, throws IllegalArgumentException with
     * appropriate message when saving data of playerOnRoster would breaks it.
     *
     * @param por    relation being checked with existing data
     * @param create if the check should be made for persisting new relation or
     *               for update existing one
     */
    private void checkConstraints(PlayerOnRoster por, boolean create) {
        TypedQuery<PlayerOnRoster> query;

        if (create) {
            query = em.createQuery("SELECT por FROM PlayerOnRoster por"
                    + " WHERE por.team.id = :team_id"
                    + " and por.player.id = :player_id", PlayerOnRoster.class);
        } else {
            query = em.createQuery("SELECT por FROM PlayerOnRoster por"
                    + " WHERE  por.id <> :id"
                    + " and por.team.id = :team_id"
                    + " and por.player.id = :player_id", PlayerOnRoster.class);
            query.setParameter("id", por.getId());
        }
        query.setParameter("team_id", por.getTeam().getId());
        query.setParameter("player_id", por.getPlayer().getId());

        if (!query.getResultList().isEmpty()) {
            throw new IllegalArgumentException("Player " + por.getPlayer()
                    + " is already on roster of " + por.getTeam());
        }

        if (create) {
            query = em.createQuery("SELECT por FROM PlayerOnRoster por"
                    + " WHERE por.team.id = :team_id"
                    + " and por.number = :number", PlayerOnRoster.class);
        } else {
            query = em.createQuery("SELECT por FROM PlayerOnRoster por"
                    + " WHERE por.id <> :id"
                    + " and por.team.id = :team_id"
                    + " and por.number = :number", PlayerOnRoster.class);
            query.setParameter("id", por.getId());
        }

        query.setParameter("team_id", por.getTeam().getId());
        query.setParameter("number", por.getNumber());
        if (!query.getResultList().isEmpty()) {
            throw new IllegalArgumentException("Number " + por.getNumber()
                    + " is already assigned on roster of " + por.getTeam());
        }
    }
}
